"""
.. module:: service
.. moduleauthor:: @author: 

The purpose of this module is ...

*Constants*:

.. seealso:: 
"""

# Imports
#
import logging
from threading import Timer

from datastore.database import *
from datastore.spaces import *
from runtime.nodelogger import NodeLogger

# Constants
#

# Classes
#

class Service(object):
    """
    This class is a container for callbacks. It ensures that the callbacks
    cb_start and cb_stop are called during node or service start and stop.
    It also provides default callbacks and convenient references to spaces.
    
    By convention:
    
        #. Sub-classes of Service do not have an __init__ method.
        #. Callbacks method names use a 'cb_' prefix.
    
    **Constants**:
    
    :param spaces: A reference to a Spaces instance.
    :param datastore:
    :param uri: A uri containing the definition of the service.
    :param node_start: A boolean to indicate that the special startup
        sequence should be used for configuration and service manager
        services.
    """

    def __init__(self, spaces=None, datastore=None, uri=None, node_start=False):
        # Constructor.
        #
        
        self.logger = NodeLogger.getLogger(".".join(['yarra', self.__module__, self.__class__.__name__]))
        # some validation
        if spaces is None:
            raise TypeError("spaces cannot be None." )
        if uri is None:
            raise TypeError("uri cannot be None.")
        if type(uri) != type(Uri()):
            raise TypeError("uri (%s) must be a Uri." % str(uri))
        #
        self.spaces = spaces
        self.datastore = datastore
        self.service_def_uri = uri
        self.service_def = None
        self.title = 'Unknown'
        self.node_start = node_start
        
    def start(self):
        """
        This method returns when the service has completed its startup.
        """
        
        if self.node_start:
            self.logger.log(logging.INFO, 
                "Node initiation start (%s)", self.service_def_uri)
            self.title = self.service_def_uri
            # node the service definition resource is not set.
        else:
            # get definition resource and check its type.
            self.service_def = self.spaces.get(self.service_def_uri)
            if self.service_def is None:
                self.logger.log(logging.ERROR, 
                    "Cannot start service without a definition resource (%s).",
                    str(self.service_def_uri))
                return
            if not self.service_def.validate(self.spaces, 
                    type='term:base/runtime/service'):
                self.logger.log(logging.ERROR,
                    "Cannot start service (%s). Invalid definition.",
                    str(self.service_def.get_type()))
                return
            self.title = self.service_def.dc_title()
        
        # the service is stopped when its definition is deleted or removed 
        # from spaces.
        self.spaces.set_watch(self.service_def_uri, self.cb_stop,
                              filter=['delete', 'remove'])
        
        self.logger.log(logging.INFO, "Service (%s) starting.", self.title)
        self.cb_start(self.service_def_uri, 'put', self.service_def)

    def async(self, callback, uri, action, res, daemon=True, name=None):
        """
        Run a call back as an asynchronous callback in its own thread.
        
        :param callback:
        :param uri: Uri
        :param action:
        :param res: Resource
        :param daemon:
        :param name:
        :rtype: Timer
        """
        
        timer = Timer(0, self.async_wrapper, args=[callback, uri, action, res])
        if name is None:
            timer.name = "[async] %s" % self.title
        else:
            timer.name = "[async] %s" % name
        # If True, terminates when the main thread exists.
        timer.daemon = daemon
        timer.start()
        return timer

    def async_wrapper(self, callback, uri, action, res):
        """
        :param callback:
        :param uri: Uri
        :param action:
        :param res: Resource
        """
        
        # Each new thread gets its own database connection.
        self.datastore = DataStore(self.datastore.filename)
        try:
            callback(uri, action, res)
        except:
            self.logger.exception(
               "Async callback failed error (%s)", sys.exc_info()[1])
        return None

    def cb_start(self, uri, action, res):
        """
        This is a default callback.
        
        :param uri: The Uri to process.
        :param action: A string that is in METHODS.
        :param res: A resource is provided for 'get', 'put' and 'remove'.
        :rtype: For process action:
        
            - 'exists' -> Boolean or None (for collection).
            - 'get'    -> A Resource or list of resources for a collection.
            - 'put'    -> The new Uri.
            - 'update' -> The new Uri.
            - 'delete' -> True if Resource deleted, None or False otherwise.
            - 'remove' -> The resource that was just deleted.
            - 'find'   -> A list of Uri's.
            - Nothing is returned for watch.
        """
        
        self.logger.log(logging.DEBUG, "Default cb_start (%s).", self.title)


    def cb_stop(self, uri, action, res):
        """
        This is a default callback that is invoked when the service is stopped.
        
        :param uri: The Uri to process.
        :param action: A string that is in METHODS.
        :param res: A resource is provided for 'get', 'put' and 'remove'.
        """
        
        self.logger.log(logging.DEBUG, "Default cb_stop (%s).", self.title)
